package com.flute.framework.search;

import java.io.File;
import java.io.IOException;

import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;

import com.flute.framework.FluteMonitor;
import com.flute.framework.LuceneRecords;
import com.flute.framework.exception.FileBlockException;
import com.flute.framework.filesystem.FileDataBlock;
import com.flute.framework.index.LuceneFileIndexCounter;
import com.flute.framework.monitor.EventLogger;
import com.flute.framework.util.ftp.FileFactory;
import com.flute.framework.util.ftp.IFileOperation;
import com.flute.tools.util.ExceptionUtil;

public class LuceneSearcherUpdater {
	private ProviderInfo info;
	private LuceneSearcherProvider provider;
	private FileDataBlock searcherA;
	private FileDataBlock searcherB;
	private FileDataBlock index;
	private static final String FOLDER_A = "A";
	private static final String FOLDER_B = "B";
	private boolean oldExisted = Boolean.FALSE;

	public LuceneSearcherUpdater(ProviderInfo info,
			LuceneSearcherProvider provider) {
		this.info = info;
		this.provider = provider;
		String location = info.getLocation();
		if (!location.endsWith("/")) {
			location = location + "/";
		}
		this.searcherA = new FileDataBlock(location + FOLDER_A, FluteMonitor
				.getSearcherProviderKey());
		if (!searcherA.existed()) {
			searcherA.create();
		}
		this.searcherB = new FileDataBlock(location + FOLDER_B, FluteMonitor
				.getSearcherProviderKey());
		if (!searcherB.existed()) {
			searcherB.create();
		}
		// XXX get oper
		IFileOperation oper = FileFactory.getFileOperation(info);
		this.index = new FileDataBlock(info.getIndexLocation(), FluteMonitor
				.getSearcherProviderKey(), oper);
	}

	public void update() {

		long eventId = EventLogger.startNewEvent(FluteMonitor
				.getSearcherProviderKey());

		info("开始新的更新，index='" + info.getIndexLocation() + "';seasrcherA='"
				+ searcherA.getPath() + "';searcherB='" + searcherB.getPath()
				+ "'");

		if (oldExisted) {
			info("旧索引正在关闭过程中,等待上一个旧索引关闭，更新取消.");
			return;
		}

		if (getCurrentPath() == null) {
			info("没有搜索器在运行.");
			if (searcherA.isEmpty() && searcherB.isEmpty()) {
				info("搜索文件夹A，B都为空，清空两个文件夹，复制索引到A");
				searcherA.clear();
				searcherB.clear();

				copyAndSet(searcherA);

			} else if (searcherA.isEmpty() && !searcherB.isEmpty()) {
				info("A为空，B不为空");
				handleOlderNewer(searcherA, searcherB, Boolean.TRUE);
			} else if (!searcherA.isEmpty() && searcherB.isEmpty()) {
				info("A不为空，B为空");
				handleOlderNewer(searcherB, searcherA, Boolean.FALSE);
			} else if (!searcherA.isEmpty() && !searcherB.isEmpty()) {
				info("A，B都不为空.");
				if (searcherA.lastModified() >= searcherB.lastModified()) {
					info("A比B更新.");
					handleOlderNewer(searcherB, searcherA, Boolean.FALSE);
				} else {
					info("B比A更新.");
					handleOlderNewer(searcherA, searcherB, Boolean.TRUE);
				}
			}
		} else if (getCurrentPath().equals(searcherA.getPath())) {
			info("A为当前正在运行的搜索器.");
			info("searcherA: " + searcherA.lastModified() + " ; index : " + index.lastModified());
			if (searcherA.lastModified() >= index.lastModified()) {
				info("A为最新的索引.");
				setSearcherInfo(searcherA);
			} else {
				info("A不为最新索引.");
				copyAndSet(searcherB);
			}
		} else if (getCurrentPath().equals(searcherB.getPath())) {
			info("B为当前正在运行的搜索器.");
			info("searcherB: " + searcherB.lastModified() + " ; index : " + index.lastModified());
			if (searcherB.lastModified() >= index.lastModified()) {
				info("B为最新的索引.");
				setSearcherInfo(searcherB);
			} else {
				info("B不为最新索引.");
				copyAndSet(searcherA);
			}
		}

		EventLogger.endEvent(EventLogger.getCurrentEventId(FluteMonitor
				.getSearcherProviderKey()));
		EventLogger.endEvent(eventId);
	}

	private void copyAndSet(FileDataBlock search) {
		if (!isIndexReady()) {
			return;
		}

		try {
			search.copyFrom(index);
			setSearcherInfo(search);
		} catch (FileBlockException e) {
			error(ExceptionUtil.getExceptionContent(e));
		}
	}

	private boolean isNewIndexComplete(String path, IFileOperation oper) {
		LuceneRecords lr = new LuceneRecords(path, oper);
		if (!lr.exists()) {
			return Boolean.FALSE;
		}

		try {
			int lrc = lr.getCount();
			int ic = new LuceneFileIndexCounter().countRecord(path);
			info("索引文件中索引个数为'" + ic + "', record.count中索引个数为'" + lrc + "'");
			return lrc == ic;
		} catch (IOException e) {
			return Boolean.FALSE;
		}
	}

	private void handleOlderNewer(FileDataBlock older, FileDataBlock newer,
			boolean aIsOlder) {
		String notName = "A";
		if (aIsOlder) {
			notName = "B";
		}
		if (newer.lastModified() >= index.lastModified()) {
			info(notName + "为最新索引.");
			setSearcherInfo(newer);
		} else {
			info(notName + "不是最新索引");
			copyAndSet(older);
		}
	}

	@SuppressWarnings("unused")
	private void check() {
		info("检查搜索器完整性.");

		if (searcherA.isWriting()) {
			warn("搜索文件夹A在写时中断，清空A.");
			searcherA.clear();
		}

		if (searcherB.isWriting()) {
			warn("搜索文件夹B在写时中断，清空B.");
			searcherB.clear();
		}
		info("检查结束.");
	}

	private boolean isIndexReady() {
		if (index.isWriting() || index.isEmpty() || index.isClearing()) {
			info("索引源正在更新或为空.");
			return Boolean.FALSE;
		} else {
			if (new LuceneRecords(index.getPath(), index.getOper()).exists()) {
				return Boolean.TRUE;
			}
			info("索引源的没有记录文档条数.");
			return Boolean.FALSE;
		}
	}

	private FileDataBlock getAnotherBlock(FileDataBlock block) {
		if (block.getPath().equals(searcherA.getPath())) {
			return searcherB;
		} else {
			return searcherA;
		}
	}

	private void setSearcherInfo(FileDataBlock block) {
		info("从文件夹'" + block.getPath() + "'生成搜索器.");
		if (block.getPath().equals(getCurrentPath())) {
			info("最新的文件夹为正在运行的搜索器文件夹，更新取消.");
		} else {
			info("检查文件夹'" + block.getPath() + "'中的索引是否是完整的.");
			if (!isNewIndexComplete(block.getPath(), block.getOper())
					|| block.isWriting() || block.isEmpty() || block.isEmpty()) {
				warn("文件夹中的索引不是完整的,清空该文件夹.");
				block.clear();
				info("更新取消.");
				if (getCurrentPath() == null) {
					info("当前没有搜索器在运行，且最新的索引不完整，尝试旧索引.");
					setSearcherInfo(getAnotherBlock(block));
				}
			} else {
				info("索引是完整的.");

				SearcherInfo searcherInfo = new SearcherInfo();
				searcherInfo.setKey(block.getPath());
				searcherInfo.setUpdateTime(block.lastModified());
				if (info.isRamDirectory()) {
					info("在文件夹'" + block.getPath() + "'新建内存搜索器...");
					try {
						LuceneSearcherInfo info = new LuceneSearcherInfo();
						IndexReader reader = IndexReader.open(new RAMDirectory(
								FSDirectory.open(new File(block.getPath()))));
						IndexSearcher searcher = new IndexSearcher(reader);

						info.setReader(reader);
						info.setSearcher(searcher);

						searcher.search(new MatchAllDocsQuery(), 100000);
						searcherInfo.setSearcher(info);

						SearcherInfo old = provider.getCurrentSearcherInfo();
						provider.setSearcherInfo(searcherInfo);
						info("成功替换搜索到文件夹'" + block.getPath() + "'.");
						closeOld(old);
					} catch (CorruptIndexException e) {
						error("在文件夹'" + block.getPath() + "'新建内存搜索器失败.\n"
								+ ExceptionUtil.getExceptionContent(e));
					} catch (IOException e) {
						error("在文件夹'" + block.getPath() + "'新建内存搜索器失败.\n"
								+ ExceptionUtil.getExceptionContent(e));
					}
				} else {
					info("在文件夹'" + block.getPath() + "'新建文件搜索器...");
					try {
						LuceneSearcherInfo info = new LuceneSearcherInfo();
						IndexReader reader = IndexReader.open(FSDirectory
								.open(new File(block.getPath())));
						IndexSearcher searcher = new IndexSearcher(reader);

						info.setReader(reader);
						info.setSearcher(searcher);

						searcher.search(new MatchAllDocsQuery(), 100000);

						searcherInfo.setSearcher(info);

						SearcherInfo old = provider.getCurrentSearcherInfo();
						provider.setSearcherInfo(searcherInfo);
						info("成功替换搜索到文件夹'" + block.getPath() + "'.");
						closeOld(old);
					} catch (CorruptIndexException e) {
						error("在文件夹'" + block.getPath() + "'新建文件搜索器失败.\n"
								+ ExceptionUtil.getExceptionContent(e));
					} catch (IOException e) {
						error("在文件夹'" + block.getPath() + "'新建文件搜索器失败.\n"
								+ ExceptionUtil.getExceptionContent(e));
					}
				}
			}
		}
	}

	private void closeOld(SearcherInfo oldInfo) {
		if (oldInfo != null) {
			Thread th = new Thread(getRunnable(oldInfo));
			th.setName("关闭搜索器线程");
			th.start();
		}
	}

	private Runnable getRunnable(final SearcherInfo searcher) {
		return new Runnable() {

			@Override
			public void run() {
				oldExisted = Boolean.TRUE;
				info("开始关闭搜索器'" + searcher.getKey() + searcher.getUpdateTime()
						+ "'...");
				LuceneSearcherInfo info = (LuceneSearcherInfo) searcher
						.getSearcher();
				Searcher se = info.getSearcher();
				IndexReader reader = info.getReader();
				if (se != null) {
					try {
						info("延迟100s关闭搜索器...");
						Thread.sleep(100000);
					} catch (InterruptedException e) {
						error("在关闭搜索器时，执行任务的线程被中断");
					}
					try {
						se.close();
						reader.close();
					} catch (IOException e) {
						error("关闭搜索器失败." + ExceptionUtil.getExceptionContent(e));
					} finally {
						se = null;
						reader = null;
					}
					info("成功关闭搜索器.");
				} else {
					info("原搜索器为空，关闭成功.");
				}
				oldExisted = Boolean.FALSE;
			}

		};
	}

	private String getCurrentPath() {
		if (provider.getCurrentSearcherInfo() == null) {
			return null;
		}

		return provider.getCurrentSearcherInfo().getKey();
	}

	private void info(String message) {
		EventLogger.info(message, FluteMonitor.getSearcherProviderKey());
	}

	private void error(String message) {
		EventLogger.error(message, FluteMonitor.getSearcherProviderKey());
	}

	private void warn(String message) {
		EventLogger.warn(message, FluteMonitor.getSearcherProviderKey());
	}
}
